package alog

import (
	"fmt"
	"runtime"
	"strings"
	"time"
)

// log level const definition
const (
	LOGDEBUG int = iota
	LOGINFO
	LOGERROR
	LOGSEVERE
)

const (
	LogBufsize = 4096
	callDepth  = 3
)

var (
	timeFormat = "2006-01-02T15:04:05.000Z07:00"

	logLevel = []string{
		"[debug ] ",
		"[info  ] ",
		"[error ] ",
		"[severe] ",
	}

	Level = map[string]int{
		"debug":  LOGDEBUG,
		"info":   LOGINFO,
		"error":  LOGERROR,
		"severe": LOGSEVERE,
	}

	defaultLogger *Logger
	LogDebug      func(*LogCtx, string, ...interface{})
	LogInfo       func(*LogCtx, string, ...interface{})
	LogError      func(*LogCtx, string, ...interface{})
	LogSevere     func(*LogCtx, string, ...interface{})
)

// LogCtx for user defined prefix and suffix add in log
type LogCtx struct {
	// Log Prefix
	Prefix func() string

	// Log Suffix
	Suffix func() string

	// Log Level
	LogLevel int
}

type Logger struct {
	logfile    *LogFile
	stackLayer int
}

// 创建新的 Log
func NewWsLog(conf *LogConf) *Logger {
	if conf.StackLayer > 10 {
		conf.StackLayer = 1
	}

	logger := &Logger{
		logfile:    CreateLogFile(conf),
		stackLayer: int(conf.StackLayer),
	}

	if defaultLogger != nil {
		return logger
	}

	defaultLogger = logger

	LogDebug = logger.LogDebug
	LogInfo = logger.LogInfo
	LogError = logger.LogError
	LogSevere = logger.LogSevere

	return logger
}

// from gozero logx/util.go
func prettyCaller(file string, line int) string {
	idx := strings.LastIndexByte(file, '/')
	if idx < 0 {
		return fmt.Sprintf(" %s:%d", file, line)
	}

	idx = strings.LastIndexByte(file[:idx], '/')
	if idx < 0 {
		return fmt.Sprintf(" %s:%d", file, line)
	}

	return fmt.Sprintf(" %s:%d", file[idx+1:], line)
}

// from gozero logx/util.go
func getCaller(depth int) string {
	_, file, line, ok := runtime.Caller(depth)
	if !ok {
		return ""
	}

	return prettyCaller(file, line)
}

func (l *Logger) logPrintf(loglv int, c *LogCtx, format string, v ...interface{}) {
	buf := make([]byte, 0, LogBufsize)

	// 时间，精确到 ms
	timestr := time.Now().Format(timeFormat) + " "
	buf = append(buf, []byte(timestr)...)

	// 日志级别
	if loglv >= LOGDEBUG && loglv <= LOGSEVERE {
		buf = append(buf, []byte(logLevel[loglv])...)
	}

	// Prefix
	if c != nil && c.Prefix != nil {
		buf = append(buf, []byte(fmt.Sprintf("%s ", c.Prefix()))...)
	}

	// User Log Content
	buf = append(buf, []byte(fmt.Sprintf(format, v...))...)

	// Suffix
	if c != nil && c.Suffix != nil {
		buf = append(buf, []byte(fmt.Sprintf(" [%s]", c.Suffix()))...)
	}

	// file:line
	for i := 0; i < l.stackLayer; i++ {
		s := getCaller(callDepth + i)
		if s == "" {
			return
		}
		buf = append(buf, []byte(s)...)
	}

	// Log End
	buf = append(buf, '\n')

	l.logfile.write(buf)
	fmt.Print(string(buf))
}

func (l *Logger) Printf(format string, args ...interface{}) {
	l.logPrintf(-1, nil, format, args...)
}

// 写入 Debug 日志
func (l *Logger) LogDebug(c *LogCtx, format string, args ...interface{}) {
	if c == nil || c.LogLevel > LOGDEBUG {
		return
	}

	l.logPrintf(LOGDEBUG, c, format, args...)
}

// 写入 Info 日志
func (l *Logger) LogInfo(c *LogCtx, format string, args ...interface{}) {
	if c == nil || c.LogLevel > LOGINFO {
		return
	}

	l.logPrintf(LOGINFO, c, format, args...)
}

// 写入 Error 日志
func (l *Logger) LogError(c *LogCtx, format string, args ...interface{}) {
	if c == nil || c.LogLevel > LOGERROR {
		return
	}

	l.logPrintf(LOGERROR, c, format, args...)
}

// 写入 Fatal 日志
func (l *Logger) LogSevere(c *LogCtx, format string, args ...interface{}) {
	if c == nil {
		return
	}

	l.logPrintf(LOGSEVERE, c, format, args...)
}
